<?php
abstract class Dao {
    private static      $daos = array();
    private static      $driverPath = 'DaoDriver';
    protected static    $isTrans = false;
    protected static    $transConns = array();
    protected           $connName = '';
    protected           $dbType = '';
    public              $dbPrefix = '';
    protected           $pdo = null;
    protected           $tables = array();
    protected           $sequences = array();
    protected           $_stmt = null;
    private             $lastQuery = '';
    
    /**
     * @param String $connName
     * @return Dao
     */
    public static function getDao($connName= DEFAULT_CONN) {
        if (false == isset(self::$daos[$connName])) {
            $dbConns = Config::get('DB_CONN');
            $connSetting = $dbConns[$connName];
            if (false == $connSetting) {
                throw new Exception('No connection  named "' . $connName . '"<br />');
            }
            $dbType = ucfirst(strtolower(str_replace('sqlite2', 'sqlite', $connSetting['DBTYPE'])));
            if ($dbType == 'Postgresql') $dbType = 'Pgsql';
            $driverName = $dbType . 'Dao';
            $daoDriver = PUMP_CORE . '/' . self::$driverPath . '/' . $driverName . '.class.php';
            if(file_exists($daoDriver))
                require_once($daoDriver);
            else 
                throw new Exception('Could not find DaoDriver "' . $driverName . '"');
            Logger::startLog('DB_CONNECT', $connName);
            $dao = new $driverName($connSetting, $connName);
            Logger::endLog('DB_CONNECT');
            self::$daos[$connName] = $dao;
        }
        
        return self::$daos[$connName];
    }
    
    abstract protected function getDsn($connSetting);
    abstract public function getMetadata($tableName);
    abstract public function createTable($tableName, $metadata);
    abstract public function getAllTables();
    abstract public function limit($strQuery, $limit);
    
    protected function __construct($connSetting) {
        $this->dbType = $connSetting['DBTYPE'];
        $this->dbPrefix = $connSetting['DBPREFIX'];
        $dsn = $this->getDsn($connSetting);
        $this->pdo = new PDO($dsn, $connSetting['DBUSER'], $connSetting['DBPASS']);
        $this->pdo->setAttribute(PDO::ATTR_CASE, Config::get('COLUMN_CASE'));
    }

    protected function setConn($connName) {
        $this->connName = $connName;
    }

    public static function startTrans() {
        self::$isTrans=true;
    }

    public static function commitTrans() {
        if (sizeof(self::$transConns) > 0) {
            foreach(self::$transConns as $connName) {
                $dao = self::getDao($connName);
                $dao->pdo->commit();
            }
            self::$isTrans = false;
            self::$transConns = array();
        }
    }

    public static function rollbackTrans() {
        if (sizeof(self::$transConns) > 0) {
            foreach(self::$transConns as $connName) {
                $dao = self::getDao($connName);
                $dao->pdo->rollback();
            }
            self::$isTrans = false;
            self::$transConns = array();
        }
    }

    protected function beginTrans($isTrans) {
        if($isTrans && false == in_array($this->connName, self::$transConns)) {
            $this->pdo->beginTransaction();
            self::$transConns[] = $this->connName;
        }
    }
    
    protected function _SQL($strQuery) {
        $strQuery = str_replace(PUMP_TABLE_PREFIX, $this->dbPrefix, $strQuery);
        $appKey = Application::getKey();
        preg_match_all('/(' . PUMP_QUOTE_STRING . '(.*)' . PUMP_QUOTE_STRING . ')/sU', $strQuery, $ar);
        if (sizeof($ar) > 0) {
            foreach ($ar[1] as $key=>$value) {
                $strQuery = str_replace($ar[1][$key], $this->pdo->quote($ar[2][$key]), $strQuery);
            }
        }
        $this->lastQuery = $strQuery;
        return $strQuery;
    }

    public function lastInsertId($seqName = '') {
        return $this->pdo->lastInsertId($seqName);
    }

    public function affectRows() {
        if (null != $this->_stmt) 
            return $this->_stmt->rowCount();
        return 0;
    }
    
    public function execute($strQuery) {
        $strQuery = $this->_SQL($strQuery);
        $this->beginTrans(self::$isTrans);
        $debug = Config::get('LOG_QUERY');
        if ($debug) Logger::startLog($this->connName . '_QUERY', $strQuery);
        if (preg_match('/^create|drop|insert|update|delete/i', trim($strQuery))) {
            $this->_stmt = $this->pdo->prepare($strQuery);
            if(false == $this->_stmt){
                if(Config::get('DEBUG')) {
                    $errorInfo = $this->pdo->errorInfo();
                    $errorInfo['SQL'] = $strQuery;
                    echo '<pre>';
                    print_r($errorInfo);
                    echo '</pre>';
                }
                exit;
            }
            $execResult = $this->_stmt->execute();
        } else {
            #mysql: show ....
            $stmt = $this->pdo->exec($strQuery);
            $execResult = false !== $stmt;
        }
        if ($debug) Logger::endLog($this->connName . '_QUERY');
        $this->checkError($stmt);
        
        return $execResult;
    }

    protected function query($strQuery, $fetchType=PUMP_FETCH_ASSOC) {
        $strQuery = $this->_SQL($strQuery);
        $this->beginTrans(self::$isTrans);
        $debug = Config::get('LOG_QUERY');
        if ($debug) Logger::startLog($this->connName . '_QUERY', $strQuery);
        $stmt = $this->pdo->prepare($strQuery);
        $stmt->execute();
        if ($debug) Logger::endLog($this->connName . '_QUERY');
        $this->checkError($stmt);
        return $stmt;
    }

    private function checkError(&$stmt=null) {
        if (null == $stmt) $stmt = $this->_stmt;
        if ($stmt) {
            $errorInfo = $stmt->errorInfo();
            if (0 != abs($errorInfo[0])) {
                $this->rollbackTrans();
                if(Config::get('DEBUG')) {
                    $this->dumpError(array('query'=>$stmt->queryString,
                                           'error'=>$errorInfo[2]));
                    throw new Exception('');
                }
            };
        }
    }
    
    private function dumpError($errorInfo) {
        echo '<pre>';
        print_r($errorInfo);
        echo '</pre>';
    }

    public function getAll($strQuery, $fetchType=PUMP_FETCH_ASSOC) {
        $stmt = $this->query($strQuery);
        return $stmt->fetchAll($fetchType);
    }

    public function getLimit($strQuery, $limit, $fetchType=PUMP_FETCH_ASSOC) {
        $strQuery = $this->limit($strQuery, $limit);
        return $this->getAll($strQuery);
    }

    public function getOne($strQuery) {
        $stmt = $this->query($strQuery);
        return $stmt->fetchColumn();
    }

    public function getRow($strQuery, $fetchType=PUMP_FETCH_ASSOC) {
        if (fasle == preg_match('/\s+limit\s+\d+/i', $strQuery)) {
            $strQuery = $this->limit($strQuery, 1);
        }
        $stmt = $this->query($strQuery);
        return $stmt->fetch($fetchType);
    }
    
    public function vacuum() {
        return true;
    }
    
    public static function close() {
        if (self::$isTrans) {
            self::commitTrans();
        }
        for ($i=0,$j=sizeof(self::$daos); $i<$j; $i++ ) {
            self::$daos[$i] = null;
        }
    }
    
    public function renderColumnValue($columnType, $columnValue) {
        if('' != strstr($columnType, 'String')) {
            return quote($columnValue);
        }  elseif ('' != strstr($columnType, 'Number')) {
            if ($columnValue < 0) {
                return -1 * abs($columnValue);
            }
            return abs($columnValue);
        } else {
            if ('Oracle' != $this->dbType) {
                return quote($columnValue);
            } else {
                //Oracle数据库要单独处理日期时间类型
            }
        }
    }
    
    public static function lastQuery($connName = DEFAULT_CONN) {
        $dao = self::getDao($connName);
        return $dao->lastQuery();
    }
};
?>